#!/usr/bin/env python
# pylint: disable-msg = C0301, F0401, R0914, R0912, W0105

"""This script is designed to offer a completely modular downloads system
In the future, it will support more databases, but for now, the only
database is flat file"""

import sys
import os
import cgitb
cgitb.enable()

# Get relevant settings from settings.py
from settings import DJANGO_PATH, DATABASE_FILE, HTTPPATH, DEBUG, VERBOSITY, HP_DIMENSIONS, MIME_PROGRAM, URL_REWRITE, EMBEDDED

# Get errors function
from errors import error, user_error

# Open the database
DATABASE_MODULE = __import__(DATABASE_FILE)
DATABASE = DATABASE_MODULE.Database()
# Get the warnings dictionary as well
DATABASE_WARNINGS = DATABASE_MODULE.WARNINGS

# We first try to import django as it could be installed system wide
try:
    import django
except ImportError: # If that fails, we import it from its local path
    # Add django to python's path
    sys.path.insert(0, DJANGO_PATH)
    try:
        import django
    except ImportError: # If that fails, there might be an extra bit on the end by accident
        sys.path.insert(0, "/".join(DJANGO_PATH.split('/')[:-1]))
        try:
            import django
        except ImportError: # If that fails, then try adding a django to the end of the path
            sys.path.insert(0, DJANGO_PATH + "/django") 
            try:
                import django
            except ImportError: # If that fails, then we give up and complain!
                if DJANGO_PATH == "":
                    print "We could not find django. Please check the DJANGO_PATH variable in settings.py"
                else:
                    print "We could not find django in or near %s. Please check the DJANGO_PATH variable in settings.py"%(DJANGO_PATH)
                sys.exit()
# This is needed so that django doesn't complain
try:
    os.environ['DJANGO_SETTINGS_MODULE']
except KeyError:
    os.environ['DJANGO_SETTINGS_MODULE'] = "settings"
    

from django.template import Template, Context, TemplateDoesNotExist
from django.template.loader import get_template


def get_cli_arguments():
    """This function gets the arguments to pydownloads via the CLI. Useful for embedding pydownloads"""
    import getopt
    # Create a database of arguments and values
    options, arguments = getopt.getopt(sys.argv[1:], "c:o:v:p:e", ["category=", "object=", "view=", "page=", "embed", "rows_rows=", "grid_rows=", "grid_cols=", "hp_view", "hp_max_rows=", "hp_rows=", "hp_cols=", "url=", "dl", "type="])
    # Set defaults of all options to None
    current_category = None
    current_category = None
    current_object = None
    current_category_view = None
    current_page = None
    current_rows = None
    current_grid_rows = None
    current_grid_columns = None
    current_hp_view = None
    current_hp_max_rows = None
    current_hp_rows = None
    current_hp_columns = None
    file_to_be_downloaded = False
    current_url = None
    current_object_type = None
    
    for option, argument in options:
        if option in ("-c", "--category"):
            current_category = argument
        elif option in ("-o", "--object"):
            current_object = argument
        elif option in ("-v", "--view"):
            # Check if it is one of the two options
            if argument in ("grid", "rows"):
                current_category_view = argument
        elif option in ("-p", "--page="):
            try:
                current_page = int(argument)
            except ValueError:
                pass
        elif option in ("-e", "--embed"):
            global EMBEDDED
            EMBEDDED = True
        elif option == "--rows_rows":
            try:
                current_rows = int(argument)
            except ValueError:
                pass
        elif option == "--grid_rows":
            try:
                current_grid_rows = int(argument)
            except ValueError:
                pass
        elif option == "--grid_cols":
            try:
                current_grid_columns = int(argument)
            except ValueError:
                pass
        elif option == "--hp_view":
            # Check if it is one of the two options
            if argument in ("grid", "rows"):
                current_hp_view = argument
        elif option == "--hp_max_rows":
            try:
                current_hp_max_rows = int(argument)
            except ValueError:
                pass
        elif option == "--hp_rows":
            try:
                current_hp_rows = int(argument)
            except ValueError:
                pass
        elif option == "--hp_cols":
            try:
                current_hp_columns = int(argument)
            except ValueError:
                pass
        elif option == "--dl":
            file_to_be_downloaded = True
        elif option == "--type":
            current_object_type = argument
        elif option == "--url":
            current_url = argument
    return {"current_category":current_category, "current_object":current_object, "current_category_view":current_category_view, "current_page":current_page, "current_rows":current_rows, "current_grid_rows":current_grid_rows, "current_grid_columns":current_grid_columns, "current_hp_view":current_hp_view, "current_hp_max_rows":current_hp_max_rows, "current_hp_rows":current_hp_rows, "current_hp_columns":current_hp_columns, "file_to_be_downloaded":file_to_be_downloaded, "current_object_type":current_object_type, "current_url":current_url}

def get_current_variables():
    """This function will get the current get variables and output a dictionary"""
    def value(variable):
        """This is a helper function to return either the variable's value,
        or in the case of a list, the first item"""
        # If the value is a string
        if type(variable) == type (""):
            return variable
        # If the value is a list
        elif type(variable) == type([]):
            # Return the first item, ignoring the rest
            return variable[0]
        
    # Get CLI arguments and use them as a fallback if there are no get variables
    cli_arguments = get_cli_arguments()
    
    # Get the url of the current page
    try:
        current_url = os.environ["REQUEST_URI"]
    except KeyError: # There would be an error when downloads.py is imported rather than run.
        current_url = None
    import cgi
    current_get_variables = cgi.FieldStorage()
    
    # Get view mode for homepage
    current_hp_view = value(current_get_variables.getvalue('hp_view'))
    if current_hp_view == None: # Use default view
        current_hp_view = cli_arguments['current_hp_view']
        if current_hp_view == None:
            current_hp_view = DATABASE.get_site_info()['home_view']
    
    # Get Homepage grid dimensions
    try:
        current_hp_rows = int(value(current_get_variables.getvalue('hp_rows')))
    except (TypeError, ValueError):
        current_hp_rows = cli_arguments['current_hp_rows']
        if current_hp_rows == None:
            current_hp_rows = DATABASE.get_site_info()['home_grid_dimensions'][0]
    try:
        current_hp_columns = int(value(current_get_variables.getvalue('hp_cols')))
    except (TypeError, ValueError):
        current_hp_columns = cli_arguments['current_hp_columns']
        if current_hp_columns == None:
            current_hp_columns = DATABASE.get_site_info()['home_grid_dimensions'][1]
    current_hp_dimensions = (current_hp_rows, current_hp_columns)
    
    # Get Homepage row limit
    try:
        current_hp_max_rows = int(value(current_get_variables.getvalue('hp_max_rows')))
    except (TypeError, ValueError):
        current_hp_max_rows = cli_arguments['current_hp_max_rows']
        if current_hp_max_rows == None:
            current_hp_max_rows = DATABASE.get_site_info()['home_max_rows']
    
    # Get category variable
    current_category = value(current_get_variables.getvalue('category'))
    if current_category == None:
        current_category = cli_arguments['current_category']
            
    # Get current object
    current_object = value(current_get_variables.getvalue('object'))
    if current_object == None:
        current_object = cli_arguments['current_object']
        
    # Get view mode
    current_category_view = value(current_get_variables.getvalue('view'))
    if current_category_view == None:
        current_category_view = cli_arguments['current_category_view']
        if current_category_view == None:
            current_category_view = DATABASE.get_site_info()['category_view']
    
    # Get rows value
    try:
        current_rows = int(value(current_get_variables.getvalue('rows_rows')))
    except (TypeError, ValueError):
        current_rows = cli_arguments['current_rows']
        if current_rows == None:
            current_rows = DATABASE.get_site_info()['category_max_rows']
    
    # Get grid dimensions value
    try:
        current_grid_rows = int(value(current_get_variables.getvalue('grid_rows')))
    except (TypeError, ValueError):
        current_grid_rows = cli_arguments['current_grid_rows']
        if current_grid_rows == None:
            current_grid_rows = DATABASE.get_site_info()['category_grid_dimensions'][0]
    try:
        current_grid_columns = int(value(current_get_variables.getvalue('grid_cols')))
    except (TypeError, ValueError):
        current_grid_columns = cli_arguments['current_grid_columns']
        if current_grid_columns == None:
            current_grid_columns = DATABASE.get_site_info()['category_grid_dimensions'][1]
    current_grid_dimensions = (current_grid_rows, current_grid_columns)
    
    # Get the page id
    try:
        current_page = int(value(current_get_variables.getvalue('page')))
    except (TypeError, ValueError):
        current_page = cli_arguments['current_page']
        if current_page == None:
            current_page = 1
    
    # Get download value
    
    if current_get_variables.getvalue('dl') == "1":
        file_to_be_downloaded = True
    else:
        file_to_be_downloaded = cli_arguments['file_to_be_downloaded']
        
    
    # Get the type to be downloaded if it has been asked for
    current_object_type = value(current_get_variables.getvalue('type'))
    if current_object_type == None:
        current_object_type = cli_arguments['current_object_type']
        
    return {'url':current_url,
            'hp_view':current_hp_view,
            'hp_max_rows':current_hp_max_rows,
            'hp_dimensions':current_hp_dimensions,
            'category':current_category,
            'object':current_object,
            'view':current_category_view,
            'rows_rows':current_rows,
            'grid_dimensions':current_grid_dimensions,
            'page':current_page,
            'download':file_to_be_downloaded,
            'type':current_object_type}

def __init__():
    """This function controls how the site works"""
    current_variables = get_current_variables()
    current_hp_view = current_variables['hp_view']
    current_hp_max_rows = current_variables['hp_max_rows']
    current_hp_dimensions = current_variables['hp_dimensions']
    current_category = current_variables['category']
    current_object = current_variables['object']
    current_category_view = current_variables['view']
    current_rows_rows = current_variables['rows_rows']
    current_grid_dimensions = current_variables['grid_dimensions']
    current_page = current_variables['page']
    file_to_be_downloaded = current_variables['download']
    current_object_type = current_variables['type']
    
    # If no type has been selected, use the first one on the object's types list
    if file_to_be_downloaded and current_object_type == None:
        # Try to download the file
        try:
            current_object_type = DATABASE.get_object(current_category, current_object)['types'][0][0]
        # If no types exist for that object, call an error
        except IndexError:
            user_error("There are no downloads for %s in the %s category."%(current_object, current_category))
    if file_to_be_downloaded == False and EMBEDDED == False:
        # Print the headers so that this file is recognised
        print "Content-type: text/html; charset=utf-8\n\n"
    # Run function depending on stage
    # If a category has been specified
    if current_category != None:
        # If an object has been specified
        if current_object != None:
            # If the file has been asked to be downloaded
            if file_to_be_downloaded:
                download_object_type(current_category, current_object, current_object_type)
            else:
                # Display the object page
                view_object(current_category, current_object)
        # Otherwise, go to category view
        else:
            # Display the category page with the right view mode
            if current_category_view == "rows":
                # Display the objects by row
                view_category_rows(current_category, current_rows_rows, current_page)
            else:
                # Default to grid mode otherwise
                view_category_grid(current_category, current_grid_dimensions, current_page)
    # If a category hasn't been specified
    else:
        # Display the home page of the script with the right view mode
        if current_hp_view == "rows":
            # Display the homepage as rows
            ### insert correct function here!
            pass
        else:
            # Default to grid mode otherwise
            ### insert correct function here!
            ## also remember to use default views from db
            pass
        home_page(current_hp_dimensions, current_page)

def generic_info():
    """This function outputs a dictionary of info relevant to every page"""
    site = {}
    site['httppath'] = HTTPPATH
    # Add current variables
    current = get_current_variables()
    # Where is the user?
    if current['category'] == None:
        site['page'] = "home"
    else:
        if current['object'] == None:
            if current['view'] == "grid":
                site['page'] = "grid"
            elif current['view'] == "rows":
                site['page'] = "rows"
        else:
            site['page'] = "object_view"
    # Add certain site info but not all of it
    banned_info = ['home_grid_template', 'home_row_template', 'category_grid_template', 'category_row_template', 'object_template']
    site_info = DATABASE.get_site_info()
    # Loop through all the site info
    for info in site_info:
        # If the info is not banned
        if info not in banned_info:
            # Add it to the info dictionary
            site[info] = site_info[info]
            
    info = {'site':site, 'current':current}
    # Add warning messages if DEBUG = True
    if DEBUG:
        # Get warnings of a certain verbosity, defined in settings.py
        warnings = DATABASE_WARNINGS.get_warnings(VERBOSITY, output="html")
        # Count the number of warnings
        number_warnings = [ level[1] for level in warnings]
        try:
            number_warnings.remove([])
        except ValueError:
            pass
        info['number_warnings'] = len(number_warnings) 
        info['warnings'] = warnings
    # Get custom options for site
    info['custom_options'] = DATABASE.get_site_info()['custom_options']
    return info

def home_page(dimensions, page_number):
    """This function will render the homepage of the script"""
    # Open the homepage template
    # We are going to use the django paginator class for this
    # because I can't be bothered to code one...
    from django.core.paginator import Paginator, InvalidPage
    
    try:
        template = get_template('home.html')
    except TemplateDoesNotExist:
        error("There is no template for the home page! Create one called home.html in a template directory.")
    # If dimensions haven't been specified, get the defaults
    rows, columns = dimensions
    if rows in (None, 0):
        rows = HP_DIMENSIONS[0]
    if columns in (None, 0):
        columns = HP_DIMENSIONS[1]
    # Get the context
    all_categories = []
    for category in DATABASE.get_categories():
        # Get category info
        info = DATABASE.get_category(category)
        # Get category name
        name = info['name']
        # Get category title
        title = info['title']
        # Get category description
        short_description = info['short_description']
        long_description = info['long_description']
        # Get category icon
        icon = info['icon']
        # Get category objects
        objects = [ DATABASE.get_object(category, category_object) for category_object in DATABASE.get_objects(category) ]
        # Add the details to the categories list
        all_categories.append({'name':name, 'title':title, 'short_description':short_description, 'long_description':long_description, 'icon':icon, 'objects':objects})
    # Get a lish of objectns for a particular page
    paginator = Paginator(all_categories, rows*columns)
    try:
        categories = paginator.page(page_number)
    except InvalidPage: # If the page number is too big, go to the last page
        categories = paginator.page(paginator.num_pages)
    # Calculate whether the category is at the end of the row
    # If the category number is divisible by the number of columns,
    # start a new row
    # Also calculate if this category is at the end of the page
    for category in categories.object_list:
        category['end_of_row'] = (categories.object_list.index(category) + 1) % columns == 0
        category['end'] = categories.object_list.index(category) == len(categories.object_list) - 1
    # Compile the context dictionary
    context = generic_info()
    context['categories'] = categories
    # Add the embedded check
    context['EMBEDDED'] = EMBEDDED
    context = Context(context)
    # Finally, render the page!
    print template.render(context).encode('utf-8')
    
def view_category_rows(category, rows, page_number=1):
    """This function will render the category objects as rows"""
    # We are going to use the django paginator class for this
    # because I can't be bothered to code one...
    from django.core.paginator import Paginator, InvalidPage
    
    # Get category info
    category_info = DATABASE.get_category(category)
    # Open the category rows template
    if category_info['row_template'] != None:
        template = Template(category_info['row_template'])
    else:
        try:
            template = get_template('rows.html')
        except TemplateDoesNotExist:
            error("There is no template for displaying objects in rows mode! Please create one called rows.html in a template directory.")
    # Get category info
    category_info = DATABASE.get_category(category)
    # if rows haven't been specified, get the category default from the database
    if rows in (None, 0):
        rows = category_info['rows_rows']
    # Get a list of objects with details to display
    all_objects = [ DATABASE.get_object(category, category_object) for category_object in DATABASE.get_objects(category)]
    # Get a list of objects for a particular page
    paginator = Paginator(all_objects, rows)
    try:
        objects = paginator.page(page_number)
    except InvalidPage: # If the page number is too big, go to the last page
        objects = paginator.page(paginator.num_pages)
    # Create the context dictionary by extending generic info
    context = generic_info()
    # Add objects variables
    context['objects'] = objects
    # Add category variables
    context['category'] = category_info
    # Get custom options for category
    context['custom_options'] = DATABASE.get_category(category)['custom_options']
    # Add the embedded check
    context['EMBEDDED'] = EMBEDDED
    # Compile the context
    context = Context(context)
    print template.render(context).encode('utf-8')

def view_category_grid(category, dimensions, page_number=1):
    """This function will render the category objects on a grid"""
    # We are going to use the django paginator class for this
    # because I can't be bothered to code one...
    from django.core.paginator import Paginator, InvalidPage
    
    # Get category info
    category_info = DATABASE.get_category(category)
    # Open the category grid template
    if category_info['grid_template'] != None:
        template = Template(category_info['grid_template'])
    else:
        try:
            template = get_template('grid.html')
        except TemplateDoesNotExist:
            error("There is no template for displaying objects in grid mode! Please create one called grid.html in a template directory.")
    # Get category info
    category_info = DATABASE.get_category(category)
    # if dimensions haven't been specified, get the category default from the database
    rows, columns = dimensions
    if rows in (None, 0):
        rows = category_info['grid_dimensions'][0]
    if columns in (None, 0):
        columns = category_info['grid_dimensions'][1]
    # Get a list of objects with details to display
    all_objects = [ DATABASE.get_object(category, category_object) for category_object in DATABASE.get_objects(category)]
    # Get a list of objects for a particular page
    paginator = Paginator(all_objects, rows*columns)
    try:
        objects = paginator.page(page_number)
    except InvalidPage: # If the page number is too big, go to the last page
        objects = paginator.page(paginator.num_pages)
    # Calculate whether the object is at the end of the row
    # If object number is divisible by the number of columns,
    # start a new row
    # Also calculate if this object is at the end of the page
    for category_object in objects.object_list:
        category_object['end_of_row'] = (objects.object_list.index(category_object) + 1) % columns == 0
        category_object['end_of_page'] = objects.object_list.index(category_object) == len(objects.object_list) - 1
    # Create the context dictionary by extending generic info
    context = generic_info()
    # Add objects variables
    context['objects'] = objects
    # Add category variables
    context['category'] = category_info
    # Get custom options for category
    context['custom_options'] = DATABASE.get_category(category)['custom_options']
    # Add the embedded check
    context['EMBEDDED'] = EMBEDDED
    # Compile the context
    context = Context(context)
    print template.render(context).encode('utf-8')

def view_object(category, category_object):
    """This function will render the object's infomation page"""
    # Get category info
    category_info = DATABASE.get_category(category)
    # Open the category's object display template
    if category_info['object_template'] != None:
        template = Template(category_info['object_template'])
    else:
        # Try using a default template
        try:
            template = get_template('base.views.html')
        except TemplateDoesNotExist:
            user_error("There is no template for object display of the %s category!"%category)
    # Get object info
    try:
        object_info = DATABASE.get_object(category, category_object)
    except TypeError:
        user_error("There is no object called %s in the %s category!"%(category_object, category))
    
    # Create the context dictionary by extending generic info
    context = generic_info()
    # Add object info
    context['object'] =  object_info
    # Add category variables
    context['category'] = category_info
    # Get custom options for category
    context['custom_options'] = DATABASE.get_object(category, category_object)['custom_options']
    # Add the embedded check
    context['EMBEDDED'] = EMBEDDED
    # Compile the context dictionary
    context = Context(context)
    # Render the page!
    print template.render(context).encode('utf-8')
    
def download_object_type(category, category_object, category_type):
    """This function will cause the object to be downloaded rather than viewed"""
    # Get the location of the object type
    if category_type in [ object_type[0] for object_type in DATABASE.get_object(category, category_object)['types']]:
        for object_type in DATABASE.get_object(category, category_object)['types']:
            if object_type[0] == category_type:
                download_file(object_type[3])
    elif DATABASE.get_object(category, category_object)['types'] > 0:
        download_file(DATABASE.get_object(category, category_object)['types'][0][3])
    else:
        user_error('There is no %s version of %s. Sorry!'%(category_type,category_object), True)
    
def download_file(filepath):
    """This fuction will force the file to be downloaded rather than viewed"""
    # First, open the file
    try:
        file_object = open(filepath, 'rb').read()
    except IOError:
        error('The file you want to download cannot be found!', True)
    # Get the mimetype of the file using the program specified in the settings
    try:
        mime = os.popen(MIME_PROGRAM%filepath).read().replace('\n', '')
    except NameError:
        mime = os.popen("file -b --mime-type %s"%filepath).read().replace('\n', '')
    # Get the file's name
    filename = filepath.split('/')[-1]
    # Output http headers and then print the file
    print "Content-Disposition: attachment; filename=" + filename
    print "Content-Type: " + mime
    print "Content-Length: %s\n"%(len(file_object))
    print file_object
    
if __name__ == "__main__":
    __init__()

